macOS Terminal Guide
Git on Mac
Complete Tutorial
From zero to version control master — every command you need to manage code, collaborate, and ship confidently using Terminal on macOS.
macOS Sequoia
Terminal.app
Git 2.x
GitHub / GitLab
Beginner → Advanced
macOS includes a basic version of Git, but the best way to get the latest is via
Homebrew — the most popular package manager for Mac. Open Terminal
(⌘ + Space → "Terminal") and run the commands below.
💡
OPEN TERMINAL FIRST
Press ⌘ Space, type "Terminal", then hit Return. All commands below
are typed directly into the Terminal prompt.
$
# Paste this entire line into Terminal
$
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
$
brew install git
==> Downloading https://ghcr.io/v2/homebrew/core/git/...
==> Installing git
🍺 /opt/homebrew/Cellar/git/2.x.x: 1,643 files
$
git --version
git version 2.47.0
✅
ALTERNATIVE: XCODE COMMAND LINE TOOLS
If you don't want Homebrew, run xcode-select --install in Terminal to install
Apple's bundled version of Git. It's slightly older but works perfectly.
Before making any commits, tell Git who you are. These values are embedded in every commit
you create. Run this once per machine.
$
git config --global user.name "Your Name"
$
git config --global user.email "you@example.com"
$
git config --global init.defaultBranch main
$
git config --global core.editor nano # or "code --wait" for VS Code
$
git config --list
user.name=Your Name
user.email=you@example.com
init.defaultbranch=main
core.editor=nano
Git tracks changes through four areas. Understanding how files move between them is the
key to mastering Git.
Working Directory
📝 Your files on disk
Where you edit, create, and delete files normally. Git is watching but hasn't saved anything yet.
Staging Area (Index)
📦 git add <file>
A preparation zone. You choose exactly which changes to include in your next commit.
Local Repository
🏠 git commit -m "msg"
The permanent record on your Mac. Every commit is a snapshot saved in the .git folder.
Remote Repository
☁️ git push origin main
GitHub, GitLab, or Bitbucket. Shared cloud storage for collaboration and backup.
Every Git project starts one of two ways: you initialize a new repository
from scratch, or you clone an existing one from a remote server.
$
mkdir my-project && cd my-project
$
git init
Initialized empty Git repository in /Users/you/my-project/.git/
$
git clone https://github.com/user/repo.git
$
cd repo
Cloning into 'repo'...
remote: Counting objects: 245, done.
Receiving objects: 100% (245/245), 1.23 MiB | 4.5 MiB/s, done.
$
git clone https://github.com/user/repo.git my-folder
The daily Git loop: check status → stage changes → commit. Every commit is a permanent,
labelled snapshot you can always return to.
$
git status
On branch main
Changes not staged for commit:
modified: index.html
Untracked files:
styles.css
$
git add index.html # stage one file
$
git add styles.css app.js # stage multiple files
$
git add . # stage ALL changes in current dir
$
git add -p # interactive: stage chunks of changes
$
git commit -m "Add navigation menu and footer"
$
git commit -am "Fix: correct typo in README" # add + commit tracked files
[main 3f2a1b7] Add navigation menu and footer
2 files changed, 47 insertions(+), 3 deletions(-)
✍️
GOOD COMMIT MESSAGES
Use the imperative mood: "Add login form" not "Added login form". Keep the
first line under 72 characters. Great messages make git log a joy to read.
$
git diff # unstaged changes vs last commit
$
git diff --staged # staged changes vs last commit
$
git diff main..dev # compare two branches
Branches let you work on features or fixes in isolation without touching the main codebase.
Think of them as parallel universes — you can switch between them instantly.
● ─── ● ─── ●──────────────────────● ← main
↘
● ─── ● ─── ● ← feature/login
git checkout -b feature/login
$
git branch # list all local branches
$
git branch feature/login # create a branch
$
git switch feature/login # switch to it (modern syntax)
$
git switch -c feature/signup # create AND switch in one step
$
git checkout -b feature/signup # older equivalent
$
git branch -a # list ALL branches (local + remote)
$
git branch -d feature/login # delete (safe — won't delete unmerged)
$
git branch -D feature/login # force-delete unmerged branch
$
git branch -m old-name new-name # rename a branch
Once your feature is done, you need to bring it back into main.
Merge preserves the branch history. Rebase rewrites it into a clean
linear sequence. Both are valid — teams usually pick one convention.
$
git switch main # go to the target branch
$
git merge feature/login # merge feature into main
$
git merge --no-ff feature/login # always create a merge commit
Merge made by the 'recursive' strategy.
login.html | 52 ++++++++++++
1 file changed, 52 insertions(+)
⚠️
MERGE CONFLICTS
When two branches edit the same lines, Git pauses and marks the conflict in the file.
Open the file, find the <<<<<<< markers, pick the correct code,
then run git add <file> and git commit to finish.
$
git switch feature/login # be on the feature branch
$
git rebase main # replay commits on top of main
$
git rebase --abort # cancel a rebase in progress
$
git rebase --continue # after resolving conflicts
A "remote" is just a bookmark pointing to another copy of the repo — usually on GitHub.
The standard remote is named origin.
$
git remote add origin https://github.com/user/repo.git
$
git remote -v # verify remotes
origin https://github.com/user/repo.git (fetch)
origin https://github.com/user/repo.git (push)
$
git push origin main # upload commits to GitHub
$
git push -u origin main # push and set upstream (first time)
$
git pull origin main # fetch + merge from GitHub
$
git fetch origin # download but don't merge
$
git push origin --delete feature/old # delete remote branch
Complete Workflow: Local project → GitHub
Create a repo on GitHub.com
Go to github.com → New → name it → don't initialize (no README). Copy the HTTPS URL.
Init, add files, and commit locally
git init → git add . → git commit -m "Initial commit"
Connect and push
git remote add origin <URL> → git push -u origin main
Work normally — push daily
Edit → git add . → git commit -m "..." → git push
Stash lets you temporarily shelve half-finished work so you can switch
context, then come back and pop it out again.
$
git stash # save dirty state
$
git stash push -m "WIP login" # stash with a name
$
git stash list # see all stashes
$
git stash pop # restore latest + delete stash
$
git stash apply stash@{1} # restore specific stash, keep it
$
git stash drop stash@{0} # delete a specific stash
$
git stash clear # delete ALL stashes
$
git clean -n # dry run: see what WOULD be deleted
$
git clean -fd # force-delete untracked files + dirs
Git gives you a safety net. No matter what you've done, there's almost always a way back.
Here are the main escape hatches, from safest to most drastic.
$
git commit --amend -m "Better message" # change message
$
git commit --amend --no-edit # add staged file, keep message
$
git restore --staged index.html # unstage (keep changes in file)
$
git restore index.html # discard working dir changes ⚠️
$
git restore . # discard ALL working dir changes ⚠️
$
git reset HEAD~1 # undo last commit, keep changes staged
$
git reset --soft HEAD~1 # undo last commit, keep changes staged
$
git reset --hard HEAD~1 # undo AND discard changes ⚠️ irreversible
$
git revert abc1234 # safe undo: creates a new reverting commit
🔥
NEVER USE --HARD ON PUSHED COMMITS
git reset --hard on commits already pushed to GitHub will break history
for your teammates. Use git revert instead — it's always safe.
git log is your time machine — browse every commit, who made it, and what changed.
$
git log # full history
$
git log --oneline # compact one-line per commit
$
git log --oneline --graph --all # visual branch graph ✨
$
git log -5 # last 5 commits
$
git log -p index.html # history with diffs for one file
$
git log --author="Jane" # commits by a specific author
$
git log --since="2 weeks ago" # commits from last 2 weeks
$
git blame index.html # see who wrote each line
$
git show abc1234 # details of a specific commit
🎨
PRETTY LOG ALIAS
Add this to ~/.zshrc or ~/.bash_profile for a beautiful log:
alias gl='git log --oneline --graph --decorate --all'
All the essential commands in one place.
| Command |
What it does |
| git init | Create a new repository in the current folder |
| git clone <url> | Download a remote repository to your Mac |
| git status | See staged, unstaged, and untracked files |
| git add . | Stage all changes in the current directory |
| git commit -m "msg" | Save staged changes as a new commit |
| git push origin main | Upload commits to GitHub |
| git pull | Fetch + merge changes from remote |
| git fetch | Download changes without merging |
| git diff | Show unstaged file differences |
| git log --oneline | Show compact commit history |
| git switch -c branch | Create and switch to a new branch |
| git merge branch | Merge a branch into the current branch |
| git rebase main | Rewrite commits on top of main |
| git stash | Temporarily save work-in-progress |
| git stash pop | Restore the last stash |
| git restore <file> | Discard changes in working directory |
| git reset --soft HEAD~1 | Undo last commit, keep changes staged |
| git revert <hash> | Safe undo: new commit that reverses changes |
| git tag -a v1.0 -m "…" | Create an annotated release tag |
| git blame <file> | See who wrote each line in a file |
The .gitignore file — create this in your project root to exclude files
from tracking (node_modules, build output, secrets, etc.)
#
~/.gitignore or project/.gitignore
node_modules/ # npm packages
dist/ # build output
.env # secret keys — never commit!
.DS_Store # macOS folder metadata
*.log # any log files